////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Διαβάζουμε από τώρα τα server modules αν και δεν είναι απαραίτητο
// να τα διαβάσουμε αυτή τη στιγμή, αλλά εδώ είναι ο πιο κατάλληλος
// χώρος.

Log.print('reading url node module');
Server.url = require('url');

Log.print('reading http node module');
Server.http = require('http');

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Ακολουθούν τα σχετικά με τις υπηρεσίες που προσφέρει ο παρών server.
// Οι υπηρεσίες ορίζονται σε δικά τους modules. Υπάρχουν modules που
// ορίζουν μια υπηρεσία, υπάρχουν όμως και modules που ορίζουν πολλές
// παρεμφερείς υπηρεσίες.

Service = {};

Log.level.push();
require('./service/stop.js');
require('./service/peparam.js');
require('./service/pektis.js');
require('./service/sinedria.js');
require('./service/feredata.js');
require('./service/anazitisi.js');
require('./service/sxesi.js');
require('./service/trapezi.js');
require('./service/prosklisi.js');
require('./service/stisimo.js');
require('./service/isodos.js');
require('./service/sizitisi.js');
require('./service/energia.js');
require('./service/kinisi.js');
require('./service/misc.js');
Log.level.pop();

// Στο αντικείμενο "router" περιέχονται οι υπηρεσίες που προσφέρει ο server.
// Σε κάθε "υπηρεσία" αντιστοιχούμε μια function που θα κληθεί με παράμετρο
// το ίδιο το αίτημα όπως αυτό διαμορφώνεται μετά τον έλεγχο και την αρχική
// επεξεργασία που υφίσταται από τον server.

Server.router = {
	'/stop': Service.stop,
	'/peparam': Service.peparam.action,
	'/pektisRefresh': Service.pektis.refresh,
	'/pektisSnapshot': Service.pektis.snapshot,
	'/pektisIp': Service.pektis.lastIp,
	'/profinfoUpdate': Service.pektis.profinfoUpdate,
	'/setPeparam': Service.pektis.setPeparam,
	'/sinedriaNea': Service.sinedria.nea,
	'/sinedriaExodos': Service.sinedria.exodos,
	'/pektisTheatis': Service.sinedria.pektisTheatis,
	'/theatisPektis': Service.sinedria.theatisPektis,
	'/thesiTheasis': Service.sinedria.thesiTheasis,
	'/feredata': Service.feredata.poll,
	'/anazitisi': Service.anazitisi.action,
	'/sxesi': Service.sxesi.action,
	'/miaPrefa': Service.trapezi.miaPrefa,
	'/prosklisiAdd': Service.prosklisi.add,
	'/prosklisiDel': Service.prosklisi.del,
	'/prosklisiDelall': Service.prosklisi.delall,
	'/prosklisiApodoxi': Service.prosklisi.apodoxi,
	'/trapeziIsodos': Service.sinedria.trapeziIsodos,
	'/trapeziExodos': Service.sinedria.trapeziExodos,
	'/trapeziRefresh': Service.sinedria.trapeziRefresh,
	'/stisimoRoloi': Service.stisimo.roloi,
	'/stisimoDiataxi': Service.stisimo.diataxi,
	'/stisimoKasa': Service.stisimo.kasa,
	'/stisimoIdiotikotita': Service.stisimo.idiotikotita,
	'/stisimoPaso': Service.stisimo.paso,
	'/stisimoAsoi': Service.stisimo.asoi,
	'/stisimoFiliki': Service.stisimo.filiki,
	'/stisimoPostel': Service.stisimo.postel,
	'/stisimoIdioktito': Service.stisimo.idioktito,
	'/stisimoKlisto': Service.stisimo.klisto,
	'/stisimoEpetiaki': Service.stisimo.epetiaki,
	'/stisimoApodoxi': Service.stisimo.apodoxi,
	'/kasaAlagi': Service.stisimo.kasaAlagi,
	'/isodos': Service.isodos.action,
	'/alagiThesis': Service.alagiThesis,
	'/katastasi': Service.katastasi,
	'/blockimage': Service.blockimage,
	'/moliviStart': Service.sizitisi.moliviStart,
	'/moliviStop': Service.sizitisi.moliviStop,
	'/sizitisiSxolio': Service.sizitisi.sizitisiSxolio,
	'/sizitisiDiagrafi': Service.sizitisi.sizitisiDiagrafi,
	'/energiaDilosi': Service.energia.dilosi,
	'/energiaSolo': Service.energia.solo,
	'/energiaAgora': Service.energia.agora,
	'/energiaSimetoxi': Service.energia.simetoxi,
	'/kinisiTest': Service.kinisi.test,
};

// Ακολουθούν υπηρεσίες που ζητούνται μεν, αλλά αγνοούνται και δεν επιστρέφουν
// αποτελέσματα ούτε εκτελούν κάποιες διεργασίες. Η υπηρεσία "favicon.ico" είναι
// κλήση που ζητείται από πολλούς browsers by default μετά την αίτηση οποιασδήποτε
// σελίδας, αλλά ο παρών server δεν χρειάζεται να απαντάει σε τέτοιου είδους
// αιτήματα.

Server.off = {
	'/favicon.ico': 0,
	'/testOff': 0
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "oriste" εκκινεί τον βασικό Node server, δηλαδή ανοίγει τη βασική πόρτα
// ακρόασης αιτημάτων και για κάθε αίτημα καλεί callback function με ορίσματα το ίδιο
// το αίτημα ("request") και το κανάλι απάντησης ("response").

Server.oriste = function() {
	Log.print('listening port ' + globals.servicePort + ' for http requests');
	Server.server = Server.http.createServer(function(request, response) {
		var nodereq, x;

		// Συμμαζεύουμε τις δομές του αιτήματος σε ένα αντικείμενο
		// για να διευκολυνθούμε στους μετέπειτα χειρισμούς.

		nodereq = new NodeRequest(request, response);

		// Αν η υπηρεσία που ζητείται είναι στις προβλεπόμενες υπηρεσίες
		// τότε καλείται η αντίστοιχη function με όρισμα το ενισχυμένο
		// αίτημα το οποίο εμπεριέχει τόσο το ίδιο το αίτημα όσο και το
		// κανάλι απάντησης.

		if (Server.router.hasOwnProperty(nodereq.service)) {
			Server.router[nodereq.service](nodereq);
			return;
		}

		// Αν η υπηρεσία που ζητείται ανήκει στις υπηρεσίες που αγνοούνται
		// κλείνουμε αμέσως το αίτημα χωρίς να στείλουμε καμία απάντηση.

		if (Server.off.hasOwnProperty(nodereq.service)) {
			nodereq.end()
			return;
		}

		// Ζητήθηκε υπηρεσία που δεν προβλέπεται από τα παραπάνω. Σ' αυτή την
		// περίπτωση απαντάμε με html σελίδα που εφόσον προβληθεί σε browser
		// δηλώνει ακριβώς αυτό το γεγονός, ότι δηλαδή η συγκεκριμένη υπηρεσία
		// δεν προβλέπεται από τον παρόντα server.

		x = nodereq.service.replace(/^\//, '');
		if (x) console.error(x + ': invalid url pathname');
		nodereq.header('html');
		nodereq.write('<html><head></head><body style="text-align: center; padding-top: 100px;">' +
			'<div style="display: inline-block; border-style: double; color: #091135; ' +
			'background-color: #85A366; padding: 5px 10px; text-align: center;">' +
			'<b>www.prefadoros.gr</b><br />' +
			'Copyright &copy; <em>Panos Papadopoulos</em> 2011-</div>');
		if (x) nodereq.write('<p><b>' + x + '</b>: <i>service not found</i></p>');
		nodereq.end('</body></head></html>');
	}).listen(globals.servicePort);
}

// Η μέθοδος "close" κλείνει τον Node server εφόσον αυτός είναι ζωντανός.
// Αυτό θα συμβεί μάλλον μόνο στο server shutdown.

Server.close = function() {
	if (!Server.hasOwnProperty('server')) return;

	Log.print('stop listening port ' + globals.servicePort);
	Server.server.close();
	delete Server.server;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "NodeRequest" δέχεται ως παραμέτρους ένα αίτημα και το κανάλι απάντησης και
// δημιουργεί αντικείμενο που περιέχει πολλά βολικά properties και μεθόδους για τους
// μετέπειτα χειρισμούς τού συγκεκριμένου αιτήματος. Το αντικείμενο που δημιουργείται
// ονομάζεται «ενισχυμένο» αίτημα και, εκτός των άλλων, περιέχει το ίδιο το αίτημα
// και το κανάλι απάντησης.

function NodeRequest(request, response) {
	var urlComponents;

	// Αρχικά εντάσσουμε στο ενισχυμένο αίτημα το ίδιο το αίτημα και το κανάλι
	// απάντησης.

	this.request = request;		// το αίτημα
	this.response = response;	// το κανάλι απάντησης

	// Εντάσσουμε επίσης το IP του αιτούντος client. Αν το αίτημα δρομολογήθηκε
	// μέσω proxy server ιχνηλατούμε το αρχικό IP.

	this.ip = request.headers['x-forwarded-for']; 
	this.ip = this.ip ? this.ip.split(',')[0] : request.connection.remoteAddress;
	this.ip = this.ip.validIp();

	// Κατόπιν εντάσσουμε δεδομένα που αφορούν στο url του αιτήματος από όπου
	// θα μπορέσουμε να αποσπάσουμε το είδος της ζητούμενης υπηρεσίας και τις
	// παραμέτρους του αιτήματος καθώς τα αιτήματα προς τον Node server γίνονται
	// με την μέθοδο GET και επομένως οι όποιες παράμετροι περνάνε ως παράμετροι
	// του url.

	urlComponents = Server.url.parse(request.url, true);
	this.service = urlComponents.pathname;
	this.url = urlComponents.query;

	// Οι μέθοδοι που ακολουθούν αφορούν στο header των δεδομένων επιστροφής.
	// Η property "redaeh" περιέχει τον τύπο των δεδομένων και by default
	// τίθεται "text/plain". Επειδή όλα τα δεδομένα επιστροφής υποτίθενται
	// "text/*", χρησιμοποιούμε μόνο το δεύτερο συνθετικό.

	this.redaeh = 'plain';
}

// Η μέθοδος "header" χρησιμοποείται ως πρώτο βήμα στην απάντηση καθορίζοντας
// το είδος των δεδομένων. Η μέθοδος μπορεί να κληθεί κατ' επανάληψη καθώς
// η πραγματική αποστολή θα γίνει σε μεταγενέστερο χρόνο.


NodeRequest.prototype.header = function(tipos) {
	if (this.redaeh === null) globals.fatal('header data already sent');
	this.redaeh = tipos;
	return this;
}

// Η μέθοδος "headerCheck" καλείται πριν την επιστροφή οποιωνδήποτε δεδομένων
// και σκοπό έχει να στείλει τα header data εφόσον αυτά δεν έχουν ήδη αποσταλεί.

NodeRequest.prototype.headerCheck = function() {
	if (this.redaeh === null) return this;

	this.response.writeHead(200, {
		'Access-Control-Allow-Origin': '*',
		'Content-type': 'text/' + this.redaeh,
	});
	this.redaeh = null;

	return this;
}

// Η μέθοδος "write" αποστέλλει τμήμα της απάντησης στον αιτούντα client.
// Η μέθοδος μπορεί να κληθεί επαναληπτικά μέχρι να ολοκληρώσουμε την
// απάντηση. Χωρίς όρισμα (ή με κενό όρισμα), η μέθοδος μπορεί να
// χρησιμοποιηθεί ως flush των header data.

NodeRequest.prototype.write = function(s) {
	this.headerCheck();
	if (s === undefined) return this;
	else if (typeof s === 'number') this.response.write(s);
	else if (typeof s !== 'string') globals.fatal('response.write: invalid data type');
	else if (s !== '') this.response.write(s);
	return this;
}

// Η μέθοδος "end" ολοκληρώνει την απάντηση προς τον αιτούντα client και
// προαιρετικά μπορεί να δεχθεί και δεδομένα τα οποία αποστέλλει ως coda
// στον client.

NodeRequest.prototype.end = function(s) {
	this.headerCheck();
	if (s === undefined) this.response.end();
	else if (typeof s === 'number') this.response.end(s);
	else if (typeof s !== 'string') globals.fatal('response.end: invalid data type');
	else if (s !== '') this.response.end(s);
	else this.response.end();
	return this;
}

// Η μέθοδος "anonimo" ελέχγει την ύπαρξη παραμέτρων "pektis" και "klidi"
// στο url. Αυτά τα στοιχεία αποτελούν τα διαπιστευτήρια του αιτούντος
// client και αν αυτά δεν υπάρχουν, τότε το αίτημα θεωρείται ανώνυμο.
// Η μέθοδος επιστρέφει true αν το αίτημα είναι ανώνυμο, αλλιώς επιστρέφει
// false και εμπλουτίζεται το ενισχυμένο αίτημα με τις δύο αυτές παραμέτρους.

NodeRequest.prototype.anonimo = function(s) {
	if (!this.url.hasOwnProperty('pektis')) {
		this.end(s ? s : 'ακαθόριστος παίκτης');
		return true;
	}

	if (!this.url.hasOwnProperty('klidi')) {
		this.end(s ? s : 'ακαθόριστο κλειδί');
		return true;
	}

	// Εφόσον όλα πήγαν καλά, προσθέτουμε στο ενισχυμένο αίτημα τα
	// properties "pektis" και "klidi".

	this.pektis = this.url.pektis;
	this.klidi = this.url.klidi;

	return false;
}

// Η μέθοδος "nosinedria" εξετάζει αν το αίτημα είναι επώνυμο και μάλιστα αν
// υπάρχει συνεδρία στον server για τον εν λόγω παίκτη. Αν το αίτημα είναι
// ανώνυμο ή δεν υπάρχει σχετική συνεδρία επιστρέφει true, αλλιώς επιστρέφει
// false και εμπλουτίζεται το ενισχυμένο αίτημα με τη σχετική συνεδρία.

NodeRequest.prototype.nosinedria = function(s) {
	if (this.anonimo(s)) return true;

	if (Skiniko.oxiSinedria(this.pektis)) {
		this.end(s ? s : 'ανύπαρκτη συνεδρία');
		return true;
	}

	// Όταν είχε δημιουργηθεί η εν λόγω συνεδρία, είχαμε αποθηκεύσει το
	// IP ως property. Όλα τα αιτήματα που γίνονται από τη συγκεκριμένη
	// συνεδρία πρέπει να έχουν το ίδιο IP.

	if (('@' + this.ip) != ('@' + Skiniko.sinedria[this.pektis].ip)) {
		this.end('invalid IP address (' + this.ip + ' <> ' + Skiniko.sinedria[this.pektis].ip + ')');
		return true;
	}

	// Προσθέτουμε τη συνεδρία ως property στο ενισχυμένο αίτημα.

	this.sinedria = Skiniko.sinedria[this.pektis];

	// Εφόσον υπάρχει συνεδρία θα υπάρχει και ο παίκτης.

	this.pektis = Skiniko.isPektis(this.pektis);
	if (!this.pektis) {
		this.end('ανύπαρκτος παίκτης συνεδρίας');
		return true;
	}

	// Αν η συνεδρία συνδέεται με κάποιο τραπέζι, θα προσθέσουμε
	// και το τραπέζι ως property του ενισχυμένου αιτήματος.

	if (this.sinedria.oxiTrapezi()) return false;

	// Αν η συνεδρία σχετίζεται με κάποιο τραπέζι, τότε προσπελαύνουμε
	// το τραπέζι και το προσθέτουμε ως property στο ενισχυμένο αίτημα.

	this.trapezi = Skiniko.isTrapezi(this.sinedria.trapezi);
	if (!this.trapezi) {
		this.end('ανύπαρκτο τραπέζι συνεδρίας');
		return true;
	}

	// Καλού κακού κάνουμε και έναν έλεγχο στη θέση.

	if (this.sinedria.oxiThesi()) {
		this.end('λανθασμένη θέση στο τραπέζι');
		return true;
	}

	return false;
}

// Η μέθοδος "isvoli" ελέχγει αν το αίτημα είναι επώνυμο, έχει σχετική συνεδρία
// και αν τα διαπιστευτήρια του αιτούντος client συμπίπτουν με αυτά της σχετικής
// συνεδρίας. Αν όλα αυτά βρεθούν εντάξει επιστρέφεται false, αλλιώς επιστρέφεται
// true.

NodeRequest.prototype.isvoli = function(s) {
	if (this.nosinedria(s)) return true;

	if (this.klidi !== this.sinedria.klidi) {
		this.end(s ? s : 'απόπειρα εισβολής');
		return true;
	}

	this.sinedria.sinedriaEpafi();
	return false;
}

NodeRequest.prototype.oxiTrapezi = function(s) {
	if (this.trapezi) return false;

	this.end(s ? s : 'Ακαθόριστο τραπέζι');
	return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Ακολουθούν δομές και μέθοδοι που αφορούν στον τερματισμό της λειτουργίας του
// Node server. Όλα αυτά τα εντάσσουμε στο singleton "exit".

Server.exit = {};

Server.exit.exiting = false;

Server.exit.action = function(reason) {
	var msg;

	if (Server.exit.exiting) return;
	Server.exit.exiting = true;

	msg = 'shutting down Node server';
	if (reason) msg += ' (' + reason + ')';
	Log.fasi.nea(msg);

	Server.close();
	Monitor.close();

	Server.exit.updateDatabase();
	
}

Server.exit.updateDatabase = function() {
	Log.print('updating database');
	Log.level.push();
	Log.print('updating "sinedria"');
	Server.exit.updateSinedria(DB.connection());
}

// Ακολουθεί function που ενημερώνει τις συνεδρίες στην database με τα στοιχεία
// που υπάρχουν στο σκηνικό. Εδώ ακολουθούμε ένα τέχνασμα· για να κρατήσουμε μια
// σειρά στις διαδικασίες και να μην δημιουργήσουμε δεκάδες ταυτόχρονα queries,
// καλούμε ανάδρομα την ίδια function για μια διαρκώς ελαττούμενη λίστα συνεδριών.

Server.exit.updateSinedria = function(conn) {
	var pektis, sinedria, query;

	for (pektis in Skiniko.sinedria) {
		sinedria = Skiniko.sinedria[pektis];
		query = 'UPDATE `sinedria` SET `poll` = NOW(), ' + (sinedria.isTrapezi() ?
			'`trapezi` = ' + sinedria.trapezi + ', `thesi` = ' + sinedria.thesi :
			'`trapezi` = NULL, `thesi` = NULL') + ', `simetoxi` = ' +
			conn.escape(sinedria.isTheatis() ? 'ΘΕΑΤΗΣ' : 'ΠΑΙΚΤΗΣ') +
			' WHERE `pektis` = ' + globals.json(sinedria.pektis);
		console.log(pektis);
		conn.query(query, function(conn) {
			sinedria.sinedriaDel();
			Server.exit.updateSinedria(conn);
		});
		return;
	}

	// Δεν έτρεξε το σώμα του παραπάνω loop, συνεπώς η λίστα έχει τελειώσει
	// και προχωρούμε στην ενημέρωση των τραπεζιών.

	Log.print('updating "trapezi"');
	Server.exit.updateTrapezi(conn);
}

Server.exit.updateTrapezi = function(conn) {
	var kodikos, trapezi, query;

	for (kodikos in Skiniko.trapezi) {
		trapezi = Skiniko.trapezi[kodikos];
		query = 'UPDATE `trapezi` SET ' +
			'`pektis1` = ' + (trapezi.pektis(1) ? conn.escape(trapezi['pektis1']) : 'NULL') + ', ' +
			'`apodoxi1` = ' + conn.escape(trapezi.apodoxi(1) ? 'ΝΑΙ' : 'ΟΧΙ') + ', ' +
			'`pektis2` = ' + (trapezi.pektis(2) ? conn.escape(trapezi['pektis2']) : 'NULL') + ', ' +
			'`apodoxi2` = ' + conn.escape(trapezi.apodoxi(2) ? 'ΝΑΙ' : 'ΟΧΙ') + ', ' +
			'`pektis3` = ' + (trapezi.pektis(3) ? conn.escape(trapezi['pektis3']) : 'NULL') + ', ' +
			'`apodoxi3` = ' + conn.escape(trapezi.apodoxi(3) ? 'ΝΑΙ' : 'ΟΧΙ') + ', ' +
			'`poll` = NOW() WHERE `kodikos` = ' + kodikos;
		console.log(kodikos);
		conn.query(query, function(conn) {
			trapezi.trapeziDel();
			Server.exit.updateTrapezi(conn);
		});
		return;
	}

	// Δεν έτρεξε το σώμα του παραπάνω loop, συνεπώς η λίστα έχει τελειώσει
	// και προχωρούμε στην ενημέρωση των τραπεζιών.

	conn.free();
	Log.level.pop();
	DB.close();
	process.reallyExit();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η function "atexit" θα κληθεί λίγο πριν βάλουμε μπροστά τον Node server
// και κανονίζει να γίνουν κάποιες ενέργειες σε περίπτωση διακοπής.

Server.atexit = function() {
	var
	i,
	stopEvent = {
		'exit':0,
		'SIGHUP':0,
		'SIGINT':0,
		'SIGQUIT':0,
		'SIGABRT':0,
		'SIGALRM':0,
		'SIGTERM':0,
	};

	for (i in stopEvent) {
		process.on(i, function() {
			Server.exit.action(i);
		});
	}
}

// Η μέθοδος "ekinisi" αποτελεί το σημείο εκκίνησης του Node server. Πρόκειται, πράγματι,
// για το τελευταίο βήμα στη διαδικασία ενεργοποίησης του Node server, αφού έχουν διαβαστεί
// όλα τα απαραίτητα modules και έχουν γίνει όλες οι ενέργειες που απαιτούνται ώστε ο Node
// server να είναι έτοιμος να εξυπηρετήσει τα εισερχόμενα αιτήματα που αφορούν σε κινήσεις
// που εκτελούν οι clients, αλλά και σε άμεση ενημέρωση των clients για τις αλλαγές σκηνικού.
// Λόγω της ασύγχρονης φύσης του Node, η διαδικασία εκκίνησης δεν μπορεί να είναι σειριακή
// αλλά πρέπει να είναι αλυσιδωτή, πράγμα που σημαίνει ότι κατά το πέρας κάθε διαδικασίας
// εκκινεί νέα διαδικασία, μέχρι να περιέλθει ο Node server σε κατάσταση ετοιμότητας.
// Τελευταία διαδικασία σε αυτή τη διαδοχή θα πρέπει να είναι το άνοιγμα της πόρτας
// ακρόασης αιτημάτων από τους clients.

Server.ekinisi = function() {
	var conn, query;

	Log.fasi.nea('starting the Node server');
	Log.print('setting database connection timeout values');
	conn = DB.connection();
	query = 'SELECT @@interactive_timeout AS `ito`, @@wait_timeout AS `wto`';
	conn.query(query, function(conn, rows) {
		conn.free();
		DB.setTimeout(rows);
		Skiniko.stisimo.enarxi();
	});
}
